/*
 * Copyright 2011 Dominik Pretzsch <dominik.pretzsch at gmail.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.blacksheep.jmoteextract.archive;

import de.blacksheep.jmoteextract.component.ProgressRenderer;
import java.io.IOException;
import java.util.Observable;
import java.util.Observer;
import de.blacksheep.jmoteextract.mvc.model.JobTableModel;
import de.blacksheep.jmoteextract.extraction.ExtractionCallable;
import de.blacksheep.jmoteextract.extraction.ExtractionQueue;
import de.blacksheep.jmoteextract.progress.ProgressManager;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Dominik Pretzsch <dominik.pretzsch at gmail.com>
 */
public class ArchiveManager extends Thread implements Observer {

    private ArchiveStorage  archiveStorage;
    private ProgressManager progressManager;
    private JobTableModel   localModel;
    private JobTableModel   remoteModel;
    
    private static final Logger LOGGER =  Logger.getLogger(ArchiveManager.class.getName());
    
    public ArchiveManager(JobTableModel localModel, 
                          JobTableModel remoteModel) {
        
        this.archiveStorage  = ArchiveStorage.getInstance();
        this.progressManager = ProgressManager.getInstance();        
        this.localModel  = localModel;
        this.remoteModel = remoteModel;
        
        this.setName(this.getClass().getName());
        this.setDaemon(true);
    }
    
    @Override
    public void run() {
        progressManager.addObserver(this);
        archiveStorage.addObserver(this);

        // TODO: Observer
        // Busy wait, I know... :-/
        while (true) {
            synchronized (this){
                try {
                    wait(0, 10000);
                } catch (InterruptedException ex) {
                    // TODO: InterruptedException
                    LOGGER.log(Level.SEVERE, "InterruptedException while run()", ex);
                }
            }
        }
    }

    public void update(Observable o, Object arg) {
        LOGGER.log(Level.FINE, "Receiving update...");
        if (arg == null) {
            LOGGER.log(Level.FINE, "arg == null. Returning...");
            return;
        }
        
        int hash = (Integer) arg;
        Archive archive = archiveStorage.get(hash);
        
        if (archive == null) {
            LOGGER.log(Level.FINE, "Archive {0} not found. Returning...", hash);
            return;
        }
        
        LOGGER.log(Level.FINE, "Processing archive {0}...", hash);

        if (o instanceof ProgressManager) {
            LOGGER.log(Level.FINE, "Event from ProgressManager for {0}...", hash);
            if (archive.isNonLocalArchive()) {
                LOGGER.log(Level.FINE, "{0} isNonLocalArchive. Try updating remote progress...", hash);
                if (progressManager.getErrorMsg(hash) == null) {
                    LOGGER.log(Level.FINE, "{0} No error occured. Updating remote progress...", hash);
                    updateNonLocalProgress(hash);
                } else {
                    LOGGER.log(Level.FINE, "{0} An error occured. Notify user...", hash);
                    setNonLocalErrorOccured(hash);
                }                
            } else {
                LOGGER.log(Level.FINE, "{0} !isNonLocalArchive. Try updating local progress...", hash);
                if (progressManager.getErrorMsg(hash) == null) {
                    LOGGER.log(Level.FINE, "{0} No error occured. Updating local progress...", hash);
                    updateLocalProgress(hash);
                    if (progressManager.isFinished(hash)) {
                        LOGGER.log(Level.FINE, "{0} isFinished. Removing...", hash);
                        removeLocalProgress(hash);
                    }
                } else {
                    LOGGER.log(Level.FINE, "{0} An error occured. Remove progress...", hash);
                    removeLocalProgress(hash);
                }
            }
        } else if (o instanceof ArchiveStorage) {
            LOGGER.log(Level.FINE, "Event from ArchiveStorage for {0}...", hash);
            if (archive.isNonLocalArchive()) {
                LOGGER.log(Level.FINE, "{0} isNonLocalArchive. pm.addObservedProgress...", hash);
                addNonLocalProgress(hash, archive);
            } else {
                LOGGER.log(Level.FINE, "{0} !isNonLocalArchive. Adding to local queue...", hash);
                addLocalProgress(hash, archive);
            }
        }
    }
    
    private void setLocalErrorOccured(int hash) {
        localModel.setProgress(
                hash,
                ProgressRenderer.INT_ERROR);
    }
    
    private void setNonLocalErrorOccured(int hash) {
        remoteModel.setProgress(
                hash,
                ProgressRenderer.INT_ERROR);
    }
    
    private void updateNonLocalProgress(int hash) {        
        remoteModel.setProgress(
                hash,
                progressManager.getProgressInPercent(hash));
    }
    
    private void updateLocalProgress(int hash) {
        localModel.setProgress(
                hash,
                progressManager.getProgressInPercent(hash));
    }
    
    private void removeLocalProgress(int hash) {
        localModel.removeRowByHash(hash);
        archiveStorage.remove(hash);
        progressManager.removeProgress(hash);
    }
    
    private void addNonLocalProgress(int hash, Archive archive) {
        try {
            progressManager.addNonLocalProgress(hash);
            LOGGER.log(Level.FINE, "{0} ... remoteModel.addJob...", hash);
            remoteModel.addJob(
                    hash,
                    archive.getFileName(),
                    archive.getAbsolutePath());
        } catch (IOException ex) {
            LOGGER.log(Level.SEVERE, "IOException while adding new non-local archive", ex);
        }
    }
    
    private void addLocalProgress(int hash, Archive archive) {
        try {
            // Get ExtractionQueue
            ExtractionQueue eq = ExtractionQueue.getInstance();
            // Create ExtractionCallable
            ExtractionCallable ec = new ExtractionCallable(archive);
            // Add to ProgressManager
            LOGGER.log(Level.FINE, "{0} ... pm.addLocalProgress...", hash);
            progressManager.addLocalProgress(
                    hash,
                    archive.getObservable());
            // Enqueue the ExtractionCallable
            eq.enqueue(ec);
            LOGGER.log(Level.FINE, "{0} ... localModel.addJob...", hash);
            localModel.addJob(
                    hash,
                    archive.getFileName(),
                    archive.getAbsolutePath());
        } catch (IOException ex) {
            LOGGER.log(Level.SEVERE, "IOException while adding new local archive", ex);
        }
    }
}
